Header file constrained_type.hpp

namespace type_safe
{
    //=== Constrained type ===//
    struct assertion_verifier;
    
    class constrain_error;
    
    struct throwing_verifier;
    
    template <typename T, typename Constraint, class Verifier = assertion_verifier>
    class constrained_type;
    
    template <typename T, class Constraint, class Verifier>
    class constrained_type<T&, Constraint, Verifier>;
    
    template <typename T, class Constraint, class Verifier = assertion_verifier>
    using constrained_ref = constrained_type<T&, Constraint, Verifier>;
    
    template <typename T, class Constraint, class Verifier>
    class constrained_modifier;
    
    template <typename T, typename Constraint, class Verifier>
    constexpr bool operator==(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
    template <typename T, typename Constraint, class Verifier>
    constexpr bool operator!=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
    template <typename T, typename Constraint, class Verifier>
    constexpr bool operator<(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
    template <typename T, typename Constraint, class Verifier>
    constexpr bool operator<=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
    template <typename T, typename Constraint, class Verifier>
    constexpr bool operator>(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
    template <typename T, typename Constraint, class Verifier>
    constexpr bool operator>=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
    
    template <class Verifier, typename T, typename Constraint>
    constexpr constrained_type<typename std::decay<T>::type, Constraint, Verifier> constrain(T&& value, Constraint c);
    
    template <typename T, typename Constraint>
    constexpr constrained_type<typename std::decay<T>::type, Constraint> constrain(T&& value, Constraint c);
    
    template <typename T, typename Constraint>
    constexpr constrained_type<typename std::decay<T>::type, Constraint, throwing_verifier> sanitize(T&& value, Constraint c);
    
    template <typename T, typename Constraint, class Verifier, typename Func, typename ... Args>
    void with(constrained_type<T, Constraint, Verifier>& value, Func&& f, Args&&... additional_args);
    
    //=== Tagged type ===//
    struct null_verifier;
    
    template <typename T, class Constraint>
    using tagged_type = constrained_type<T, Constraint, null_verifier>;
    
    template <typename T, class Constraint>
    using tagged_ref = constrained_ref<T, Constraint, null_verifier>;
    
    template <typename T, typename Constraint>
    constexpr tagged_type<typename std::decay<T>::type, Constraint> tag(T&& value, Constraint c);
    
    namespace constraints
    {
        struct non_null;
        
        class non_empty;
        
        struct non_default;
        
        struct non_invalid;
        
        struct owner;
    }
}

Struct type_safe::assertion_verifier

struct assertion_verifier
{
    template <typename Value, typename Predicate>
    static constexpr typename std::decay<Value>::type verify(Value&& val, const Predicate& p);
};

A Verifier for ts::constrained_type that DEBUG_ASSERTs the constraint.

If TYPE_SAFE_ENABLE_PRECONDITION_CHECKS is true, it will assert that the value fulfills the predicate and returns it unchanged. If assertions are disabled, it will just return the value unchanged.

Class type_safe::constrain_error

class constrain_error
: public std::logic_error
{
public:
    constrain_error();
};

The exception class thrown by the ts::throwing_verifier.

Struct type_safe::throwing_verifier

struct throwing_verifier
{
    template <typename Value, typename Predicate>
    static constexpr typename std::decay<Value>::type verify(Value&& val, const Predicate& p);
};

A Verifier for ts::constrained_type that throws an exception in case of failure.

Unlike ts::assertion_verifier, it will always check the constrain. If it is not fulfilled, it throws an exception of type ts::constrain_error, otherwise return the original value unchanged.

Notes: ts::assertion_verifier is the default, because a constrain violation is a logic error, usually done by a programmer. Use this one only if you want to use ts::constrained_type with unsanitized user input, for example.

Class template type_safe::constrained_type

template <typename T, typename Constraint, class Verifier = assertion_verifier>
class constrained_type
{
public:
    using value_type = typename std::remove_cv<T>::type;
    
    using constraint_predicate = Constraint;
    
    constexpr constrained_type(const value_type& value, constraint_predicate predicate = {});
    constexpr constrained_type(value_type&& value, constraint_predicate predicate = {}) noexcept('hidden');
    
    constexpr constrained_type(const constrained_type& other);
    
    ~constrained_type() noexcept = default;
    
    constexpr constrained_type& operator=(const value_type& other);
    
    constexpr constrained_type& operator=(value_type&& other) noexcept('hidden');
    
    constexpr constrained_type& operator=(const constrained_type& other);
    
    friend constexpr void swap(constrained_type& a, constrained_type& b) noexcept('hidden');
    
    template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
    constrained_modifier<T, Constraint, Verifier> modify() noexcept;
    
    constexpr value_type&& release() && noexcept;
    
    constexpr const value_type& operator*() const noexcept;
    
    constexpr const value_type* operator->() const noexcept;
    
    constexpr const value_type& get_value() const noexcept;
    
    constexpr const constraint_predicate& get_constraint() const noexcept;
};

A value of type T that always fulfills the predicate Constraint.

The Constraint is checked by the Verifier. The Constraint can also provide a nested template is_valid<T> to statically check types. Those will be checked regardless of the Verifier.

If T is const, the modify() function will not be available, you can only modify the type by assigning a completely new value to it.

Requires: T must not be a reference, Constraint must be a moveable, non-final class where no operation throws, and Verifier must provide a static function [const] T[&] verify(const T&, const Predicate&). The return value is stored and it must always fulfill the predicate. It also requires that no const operation on T may modify it in a way that the predicate isn't fulfilled anymore. \notes Additional requirements of the Constraint depend on the Verifier used. If not stated otherwise, a Verifier in this library requires that the Constraint is a Predicate for T.

Constructor type_safe::constrained_type::constrained_type

(1)  constexpr constrained_type(const value_type& value, constraint_predicate predicate = {});

(2)  constexpr constrained_type(value_type&& value, constraint_predicate predicate = {}) noexcept('hidden');

Effects: Creates it giving it a valid value and a predicate. The value will be copied(1)/moved(2) and verified.

Throws: Anything thrown by the copy(1)/move(2) constructor of value_type or the Verifier if the value is invalid.

Copy constructor type_safe::constrained_type::constrained_type

constexpr constrained_type(const constrained_type& other);

Effects: Copies the value and predicate of other.

Throws: Anything thrown by the copy constructor of value_type.

Requires: Constraint must be copyable.

Destructor type_safe::constrained_type::~constrained_type

~constrained_type() noexcept = default;

Effects: Destroys the value.

Assignment operator type_safe::constrained_type::operator=

constexpr constrained_type& operator=(const value_type& other);

Effects: Same as assigning constrained_type(other, get_constraint()).release() to the stored value. It will invoke copy(1)/move(2) constructor followed by move assignment operator. \throws Anything thrown by the copy(1)/move(2) constructor or move assignment operator of value_type, or the Verifier if the value is invalid. If the value is invalid, nothing will be changed. \requires Constraint must be copyable. \group assign_value

Assignment operator type_safe::constrained_type::operator=

(1)  constexpr constrained_type& operator=(value_type&& other) noexcept('hidden');

Assignment operator type_safe::constrained_type::operator=

constexpr constrained_type& operator=(const constrained_type& other);

Effects: Copies the value and predicate from other.

Throws: Anything thrown by the copy assignment operator of value_type.

Requires: Constraint must be copyable.

Function type_safe::swap

friend constexpr void swap(constrained_type& a, constrained_type& b) noexcept('hidden');

Effects: Swaps the value and predicate of a a and b.

Throws: Anything thrown by the swap function of value_type.

Requires: Constraint must be swappable.

Function template type_safe::constrained_type::modify

template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
constrained_modifier<T, Constraint, Verifier> modify() noexcept;

Returns: A proxy object to provide verified write-access to the stored value.

Notes: This function does not participate in overload resolution if T is const.

Function type_safe::constrained_type::release

constexpr value_type&& release() && noexcept;

Effects: Moves the stored value out of the constrained_type, it will not be checked further.

Returns: An rvalue reference to the stored value.

Notes: After this function is called, the object must not be used anymore except as target for assignment or in the destructor.

Operator type_safe::constrained_type::operator*

constexpr const value_type& operator*() const noexcept;

Dereference operator.

Returns: A const reference to the stored value.

Operator type_safe::constrained_type::operator->

constexpr const value_type* operator->() const noexcept;

Member access operator.

Returns: A const pointer to the stored value.

Function type_safe::constrained_type::get_value

constexpr const value_type& get_value() const noexcept;

Returns: A const reference to the stored value.

Function type_safe::constrained_type::get_constraint

constexpr const constraint_predicate& get_constraint() const noexcept;

Returns: The predicate that determines validity.


Class template type_safe::constrained_type<T&, Constraint, Verifier>

template <typename T, class Constraint, class Verifier>
class constrained_type<T&, Constraint, Verifier>
{
public:
    using value_type = T;
    
    using constraint_predicate = Constraint;
    
    constexpr constrained_type(T& value, constraint_predicate predicate = {});
    
    template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
    constrained_modifier<T&, Constraint, Verifier> modify() noexcept;
    
    constexpr const value_type& operator*() const noexcept;
    
    constexpr const value_type* operator->() const noexcept;
    
    constexpr const value_type& get_value() const noexcept;
    
    constexpr const constraint_predicate& get_constraint() const noexcept;
};

Specialization of ts::constrained_type for references.

It models a reference to a value that always fulfills the given constraint. The value must not be changed by other means, it is thus perfect for function parameters.

Constructor type_safe::constrained_type<T&, Constraint, Verifier>::constrained_type

constexpr constrained_type(T& value, constraint_predicate predicate = {});

Effects: Binds the reference to the given object.

Function template type_safe::constrained_type<T&, Constraint, Verifier>::modify

template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
constrained_modifier<T&, Constraint, Verifier> modify() noexcept;

Returns: A proxy object to provide verified write-access to the referred value.

Notes: This function does not participate in overload resolution if T is const.

Operator type_safe::constrained_type<T&, Constraint, Verifier>::operator*

constexpr const value_type& operator*() const noexcept;

Dereference operator.

Returns: A const reference to the referred value.

Operator type_safe::constrained_type<T&, Constraint, Verifier>::operator->

constexpr const value_type* operator->() const noexcept;

Member access operator.

Returns: A const pointer to the referred value.

Function type_safe::constrained_type<T&, Constraint, Verifier>::get_value

constexpr const value_type& get_value() const noexcept;

Returns: A const reference to the referred value.

Function type_safe::constrained_type<T&, Constraint, Verifier>::get_constraint

constexpr const constraint_predicate& get_constraint() const noexcept;

Returns: The predicate that determines validity.


Alias template type_safe::constrained_ref

template <typename T, class Constraint, class Verifier = assertion_verifier>
using constrained_ref = constrained_type<T&, Constraint, Verifier>;

Alias for ts::constrained_type<T&>.

Class template type_safe::constrained_modifier

template <typename T, class Constraint, class Verifier>
class constrained_modifier
{
public:
    using value_type = typename constrained_type<T, Constraint, Verifier>::value_type;
    
    constrained_modifier(constrained_modifier&& other) noexcept;
    
    ~constrained_modifier() noexcept(false);
    
    constrained_modifier& operator=(constrained_modifier&& other) noexcept;
    
    value_type& operator*() noexcept;
    
    value_type* operator->() noexcept;
    
    value_type& get() noexcept;
};

A proxy class to provide write access to the stored value of a ts::constrained_type.

The destructor will verify the value again.

Move constructor type_safe::constrained_modifier::constrained_modifier

constrained_modifier(constrained_modifier&& other) noexcept;

Effects: Move constructs it. other will not verify any value afterwards.

Destructor type_safe::constrained_modifier::~constrained_modifier

~constrained_modifier() noexcept(false);

Effects: Verifies the value, if there is any.

Assignment operator type_safe::constrained_modifier::operator=

constrained_modifier& operator=(constrained_modifier&& other) noexcept;

Effects: Move assigns it. other will not verify any value afterwards.

Operator type_safe::constrained_modifier::operator*

value_type& operator*() noexcept;

Dereference operator.

Returns: A reference to the stored value.

Requires: It must not be in the moved-from state.

Operator type_safe::constrained_modifier::operator->

value_type* operator->() noexcept;

Member access operator.

Returns: A pointer to the stored value.

Requires: It must not be in the moved-from state.

Function type_safe::constrained_modifier::get

value_type& get() noexcept;

Returns: A reference to the stored value.

Requires: It must not be in the moved-from state.


Constrained type comparison

(1)  template <typename T, typename Constraint, class Verifier>
     constexpr bool operator==(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');

(2)  template <typename T, typename Constraint, class Verifier>
     constexpr bool operator!=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');

(3)  template <typename T, typename Constraint, class Verifier>
     constexpr bool operator<(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');

(4)  template <typename T, typename Constraint, class Verifier>
     constexpr bool operator<=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');

(5)  template <typename T, typename Constraint, class Verifier>
     constexpr bool operator>(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');

(6)  template <typename T, typename Constraint, class Verifier>
     constexpr bool operator>=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');

Compares a ts::constrained_type.

Returns: The result of the comparison of the underlying value.

Notes: The comparison operators do not participate in overload resolution, unless the stored type provides them as well.

Function template type_safe::constrain

template <class Verifier, typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint, Verifier> constrain(T&& value, Constraint c);

Creates a ts::constrained_type.

Returns: A ts::constrained_type with the given value, Constraint and Verifier.

Function template type_safe::constrain

template <typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint> constrain(T&& value, Constraint c);

Creates a ts::constrained_type with the default verifier, ts::assertion_verifier.

Returns: A ts::constrained_type with the given value and Constraint.

Requires: As it uses a DEBUG_ASSERT to check constrain, the value must be valid.

Function template type_safe::sanitize

template <typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint, throwing_verifier> sanitize(T&& value, Constraint c);

Creates a ts::constrained_type using the ts::throwing_verifier.

Returns: A ts::constrained_type with the given value and Constraint.

Throws: A ts::constrain_error if the value isn't valid, or anything else thrown by the constructor.

Notes: This is meant for sanitizing user input, using a recoverable error handling strategy.

Function template type_safe::with

template <typename T, typename Constraint, class Verifier, typename Func, typename ... Args>
void with(constrained_type<T, Constraint, Verifier>& value, Func&& f, Args&&... additional_args);

With operation for ts::constrained_type.

Effects: Calls f with a non-const reference to the stored value of the ts::constrained_type. It checks that f does not change the validity of the object. \notes The same behavior can be accomplished by using the modify() member function.

Struct type_safe::null_verifier

struct null_verifier
{
    template <typename Value, typename Predicate>
    static constexpr Value&& verify(Value&& v, const Predicate&);
};

A Verifier for ts::constrained_type that doesn't check the constraint.

It will simply return the value unchanged, without any checks.

Notes: It does not impose any additional requirements on the Predicate.

Alias template type_safe::tagged_type

template <typename T, class Constraint>
using tagged_type = constrained_type<T, Constraint, null_verifier>;

An alias for ts::constrained_type that never checks the constraint.

It is useful for creating tagged types: The Constraint - which does not need to be a predicate anymore - is a "tag" to differentiate a type in different states. For example, you could have a "sanitized" value and a "non-sanitized" value that have different types, so you cannot accidentally mix them. \notes It is only intended if the Constraint cannot be formalized easily and/or is expensive. Otherwise ts::constrained_type is recommended as it does additional runtime checks in debug mode.

Alias template type_safe::tagged_ref

template <typename T, class Constraint>
using tagged_ref = constrained_ref<T, Constraint, null_verifier>;

An alias for ts::tagged_type with reference.

Function template type_safe::tag

template <typename T, typename Constraint>
constexpr tagged_type<typename std::decay<T>::type, Constraint> tag(T&& value, Constraint c);

Creates a new ts::tagged_type.

Returns: A ts::tagged_type with the given value and Constraint.

Struct type_safe::constraints::non_null

struct non_null
{
    template <typename T>
    struct is_valid
    : std::true_type
    {
    };
    
    template <typename T>
    constexpr bool operator()(const T& ptr) const noexcept;
};

A Constraint for the ts::constrained_type.

A value of a pointer type is valid if it is not equal to nullptr. This is borrowed from GSL's non_null.

Class type_safe::constraints::non_empty

class non_empty
{
public:
    template <typename T>
    constexpr bool operator()(const T& t) const;
};

A Constraint for the ts::constrained_type.

A value of a container type is valid if it is not empty. Empty-ness is determined with either a member or non-member function.

Struct type_safe::constraints::non_default

struct non_default
{
    template <typename T>
    constexpr bool operator()(const T& t) const noexcept('hidden');
};

A Constraint for the ts::constrained_type.

A value is valid if it not equal to the default constructed value.

Struct type_safe::constraints::non_invalid

struct non_invalid
{
    template <typename T>
    constexpr bool operator()(const T& t) const noexcept('hidden');
};

A Constraint for the ts::constrained_type.

A value of a pointer-like type is valid if the expression !value is false.

Struct type_safe::constraints::owner

struct owner
{
};

A Constraint for the ts::tagged_type.

It marks an owning pointer. It is borrowed from GSL's non_null.

Notes: This is not actually a predicate.